package com.atguigu.utils;

import org.apache.poi.ss.formula.functions.T;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * @author zhengkaiyuan
 * @version 1.0
 * @create 2021-06-26 20:13
 */
public class mergeDownLoadUtils {

    /**
     * 针对多个集合的上传
     * @param fileName 指定的上传文件名称
     * @param list     指定的上传数据
     * @param filePath 指定的上传文件路径
     * @param <T>
     * @return 带返回文件名带有下载条数
     * @throws Exception
     */
    public static <T> String createExcelToNameMore(String fileName, List<List<T>> list, String filePath) throws Exception {
        //在内存中创建一个Excel文件
        XSSFWorkbook workbook = new XSSFWorkbook();
        //创建sheet页并添加数据
        creatSheetAndFillerDataMore(workbook, list);
        //拼接地址 防止文件名称重复
        String address = filePath + File.separator + fileName + ".xlsx";
        //通过输出流将workbook对象下载到磁盘
        FileOutputStream out = new FileOutputStream(address);
        workbook.write(out);
        out.flush();//刷新
        out.close();//关闭
        workbook.close();
        return fileName + ".xlsx";
    }


    private static <T> void creatSheetAndFillerDataMore(XSSFWorkbook workbook, List<List<T>> list) throws Exception {
        if (list == null || list.size() == 0) {
            throw new RuntimeException("没有数据，无法下载！");
        }
        //遍历集合
        for (List<T> everyList : list) {
            //获取当前集合元素类型
            Class<?> elementType = getElementType(everyList);
            //获取类元信息
            List<String> meta = getMeta(elementType);
            //封装成map
            List<Map<String, String>> maps = wrapMap(everyList);
            //获取当前第一个sheet页的名称
            String sheetName = getSheetName(elementType);
            //创建工作表，指定工作表名称
            XSSFSheet sheet = workbook.createSheet(sheetName);
            //创建行，0表示第一行
            XSSFRow row = sheet.createRow(0);
            //第一行初始化表头信息
            for (int i = 0; i < meta.size(); i++) {
                row.createCell(i).setCellValue(meta.get(i));
            }
            //定义每一行的索引位置(即添加数据的索引位置)
            int index = 1;
            //循环向excel中添加数据
            for (Map<String, String> rowData : maps) {
                row = sheet.createRow(index++);
                fillerDate(row, rowData, meta);
            }
        }
    }


    /**
     * 获取类元信息
     *
     * @param instance
     * @return
     */
    private static List<String> getMeta(Class<?> instance) {
        //获取当前对象的所有类元信息
        Field[] declaredFields = instance.getDeclaredFields();
        //定义返回数组
        List<String> meta = new ArrayList<>();
        int index = 0;
        //遍历当前字段的名称
        for (Field declaredField : declaredFields) {
            String fieldName = declaredField.getName();
            if (fieldName.contains("img") || judge(declaredField)) {
                continue;
            } else {
                meta.add(fieldName);
            }
        }
        return meta;
    }


    /**
     * 需要封装成map的集合
     *
     * @param list
     * @param <T>
     * @return
     */
    private static <T> List<Map<String, String>> wrapMap(List<T> list) throws Exception {
        List<Map<String, String>> maps = new ArrayList<>();
        //获取当前集合中数据的类型,以及对应的类元信息
        Class<?> instance = getElementType(list);
        //获取当前实例的字段
        Field[] declaredFields = instance.getDeclaredFields();
        //遍历集合
        for (T element : list) {
            //每一个map
            Map<String, String> valueMap = new HashMap<>();
            //遍历字段
            for (Field declaredField : declaredFields) {
                //获取当前字段名称
                String filedName = declaredField.getName();
                //设置访问权限
                declaredField.setAccessible(true);
                //得到当前对象真实的值
                Object value = declaredField.get(element);
                if (value == null) {
                    valueMap.put(filedName, "暂无");
                } else if (judge(declaredField)) {
                    continue;
                } else {
                    valueMap.put(filedName, value.toString());
                }
            }
            maps.add(valueMap);
        }
        return maps;
    }


    /**
     * @param row         为从第二行添加数据的行号
     * @param rowData     为这一行需要添加的数据
     * @param originTitle 获取第一行的表头信息
     */
    private static void fillerDate(XSSFRow row, Map<String, String> rowData, List<String> originTitle) {
        for (int i = 0; i < originTitle.size(); i++) {
            String value = rowData.get(originTitle.get(i));
            row.createCell(i).setCellValue(value);
        }
    }


    /**
     * 获取集合当中存储的数据的类型
     *
     * @param list
     * @param <T>
     * @return
     */
    private static <T> Class<?> getElementType(List<T> list) {
        return list.get(0).getClass();
    }


    /**
     * \
     * 获取当前sheet页名称
     *
     * @param clazz
     * @return
     */
    private static String getSheetName(Class<?> clazz) {
        String sheetNameTemp = clazz.getTypeName();
        int index = sheetNameTemp.lastIndexOf('.');
        return sheetNameTemp.substring(index+1);
    }

    /**
     * 判断当前是否为参数化泛型
     *
     * @param declaredField
     * @return
     */
    private static boolean judge(Field declaredField) {
        if (declaredField.getGenericType() instanceof ParameterizedType) {
            Type genericType = declaredField.getGenericType();
            //ParameterizedType就是参数化类型
            ParameterizedType pt = (ParameterizedType) genericType;
            //getRawType()返回Tpye，得到“<>”前面的类型，比如List<String>
            if (pt.getRawType().equals(List.class)) {
                System.out.println("pt = " + pt.getRawType());
                System.out.println("List.class = " + List.class);
                return true;
            }
        }
        return false;
    }


/*======================================单集合=================================================*/
    /**
     * @param fileName 指定的上传文件名称
     * @param list     指定的上传数据
     * @param filePath 指定的上传文件路径
     * @param <T>
     * @return 带返回文件名带有下载条数
     * @throws Exception
     */
    public static <T> String createExcelToName(String fileName, List<T> list, String filePath) throws Exception {
        //在内存中创建一个Excel文件
        XSSFWorkbook workbook = new XSSFWorkbook();
        //创建sheet页并添加数据
        creatSheetAndFillerData(workbook, list);
        //拼接地址 防止文件名称重复
        String address = filePath + File.separator + fileName + ".xlsx";
        //通过输出流将workbook对象下载到磁盘
        FileOutputStream out = new FileOutputStream(address);
        workbook.write(out);
        out.flush();//刷新
        out.close();//关闭
        workbook.close();
        return fileName + ".xlsx";
    }



    /**
     * 创建sheet页并填充数据(针对多个sheet页)
     *
     * @param workbook 当前工作簿
     * @param list     当前数据
     * @param <T>
     */
    private static <T> void creatSheetAndFillerData(XSSFWorkbook workbook, List<T> list) throws Exception {
        if (list == null || list.size() == 0) {
            throw new RuntimeException("没有数据，无法下载！");
        }
        //声明一个lists集合 用于分类接受数据
        List<List<T>> lists= new ArrayList<>();
        //首先将当前集合的数据保存到集合当中
        lists.add(list);
        //获取当前集合存储的数据类型
        Class<?> elementType = getElementType(list);
        //获取类元信息
        List<String> meta = getMeta(elementType);
        //获取当前字段的信息
        Field[] declaredFields = elementType.getDeclaredFields();
        //判断当前获取的类元信息的长度是否小于当前字段集合-1，如果小于则排除了集合类型，需要进行数据抽取分类显示
        if (declaredFields.length-1 > meta.size()){
            //进行数据的分类抽取
            dataClassify(list,declaredFields,lists);
        }
        //遍历集合
        for (List<T> everyList : lists) {
            //获取当前集合元素类型
            Class<?> everyElementType = getElementType(everyList);
            //获取类元信息
            List<String> everyMeta = getMeta(everyElementType);
            //封装成map
            List<Map<String, String>> maps = wrapMap(everyList);
            //获取当前第一个sheet页的名称
            String sheetName = getSheetName(everyElementType);
            //创建工作表，指定工作表名称
            XSSFSheet sheet = workbook.createSheet(sheetName);
            //创建行，0表示第一行
            XSSFRow row = sheet.createRow(0);
            //第一行初始化表头信息
            for (int i = 0; i < everyMeta.size(); i++) {
                row.createCell(i).setCellValue(everyMeta.get(i));
            }
            //定义每一行的索引位置(即添加数据的索引位置)
            int index = 1;
            //循环向excel中添加数据
            for (Map<String, String> rowData : maps) {
                row = sheet.createRow(index++);
                fillerDate(row, rowData, everyMeta);
            }
        }
    }

    /**
     * 数据分类
     *
     * @param list           原始数据集合
     * @param declaredFields 当前集合的字段类型
     * @param lists          用于收集数据
     * @param <T>
     */
    private static <T> void dataClassify(List<T> list, Field[] declaredFields, List<List<T>> lists) throws IllegalAccessException {
        //判断当前集合是否存在list类型的字段.如果为null，则当前集合不存在list字段，则不需要进行下面的迭代
        Field judgeHasListFiled = judgeHasListFiled(declaredFields);
        if (judgeHasListFiled == null){
            return;
        }
        //如果存在,收集当前集合
        List<T> listTemp = new ArrayList<>();
        //遍历集合
        for (T element : list) {
            //获取集合中的集合
            judgeHasListFiled.setAccessible(true);
            List newSheet = (List) judgeHasListFiled.get(element);
            if (newSheet != null && newSheet.size() > 0) {
                listTemp.addAll(newSheet);
            }
        }
        if (listTemp.size() > 0) {
            //添加到集合当中
            lists.add(listTemp);
            //获取新集合当中的数据元素类型
            Class<?> elementType = getElementType(listTemp);
            //获取字段
            Field[] field = elementType.getDeclaredFields();
            dataClassify(listTemp, field, lists);
        }
    }

    /**
     * 判断当前集合是否存在list字段,如果存在返回list集合字段
     * @return
     */
    private static <T> Field judgeHasListFiled(Field[] declaredFields){
        for (Field declaredField : declaredFields) {
            //判断当前字段是否
            if (judge(declaredField)) {
               return declaredField;
            }
        }
        return null;
    }
}
